#include <Globals.h>
#include <Asm.h>
#include <ProcessManager.h>
#include <SerialDriver.h>
#include <DescriptorTables.h>
#include <SyscallDispatcher.h>
#include <RpcCall.h>

// This variable holds the kernel stack of the current program. It is defined
// so that classes can use START_KERNEL/END_KERNEL without #include ing
// ProcessManager.h and Process.h.
volatile u32int currentKernelStack;

// Some operations require interrupts disabled. We create a stack here - 
// when the index reaches zero, we enable interrupts. when it becomes nonzero,
// we disable interrupts. That way, if two nested start/stop calls take place,
// the inner will not disrupt the outer. TODO: for SMP make this a semaphore!
volatile u32int interruptsEnabledStack = 0;
void criticalSection()
{
  if (interruptsEnabledStack == 0)
  {
    stopInterrupts();
  }
  interruptsEnabledStack++;
}
void endCriticalSection()
{
  interruptsEnabledStack--;
  if (interruptsEnabledStack == 0)
  {
    startInterrupts();
  }
}

extern "C"
{
  void switchProcess(Address,Address,Address,Address);
}

extern Address executeRpcRequestsWrapper;
extern Address executeRpcRequestsKernelWrapper;

/**
  This warrants a function description.
  
  This is called from the wrapper function executeRpcRequestsWrapper.
  The wrapper pushes two parameters (that are passed in registers esi,edi):
  call and process. These are used to execute the call.
  
  At the end, we overwrite 'process' with the EIP we want to return to. We then
  trample all the register values (except EAX, as that is used as a temp variable by gcc)
  to their values before the syscall was called.
  We also push EFLAGS then popf to load them. Finally we trample EAX then 'leave'.
  At this point we advance (increase) the stack pointer by 8, that is, we jump over
  the return address and first parameter. Remember that we overwrote the second parameter
  with the *real* return address. We then 'ret' to that.
  
  This is done to prevent registers like EBP, EAX etc being trampled while still returning
  to a custom address. <borat>High Five!!!</borat>
**/
extern "C" void executeRpcRequests(RpcCall *call, Process *process)
{
  process->executeRpcCalls(call);

  u32int eip = call->eip;
  u32int eax = call->eax;
  u32int ebx = call->ebx;
  u32int ecx = call->ecx;
  u32int edx = call->edx;
  u32int esi = call->esi;
  u32int edi = call->edi;
  u32int eflags = call->eflags;
  
  // If we should, destroy the RpcCall object.
  if (call->destroyable)
    asm volatile("int $30" : : "a" (SYSCALL_UFREE), "b" ((int)call));

  // Overwrite 'process', which will become our return address.
  asm volatile("mov %0, 0xc(%%ebp)" : : "r" (call->eip));
  asm volatile("mov %0, %%ebx" : : "m" (call->ebx));
  asm volatile("mov %0, %%ecx" : : "m" (call->ecx));
  asm volatile("mov %0, %%edx" : : "m" (call->edx));
  asm volatile("mov %0, %%esi" : : "m" (call->esi));
  asm volatile("mov %0, %%edi" : : "m" (call->edi));
  asm volatile("push %0" : : "m" (call->eflags));
  asm volatile("popf");
  // The EAX statement must go at the end as the compiler uses EAX as a temporary variable
  // in all the above statements.
  asm volatile("mov %0, %%eax" : : "m" (call->eax));
  asm volatile("leave");
  asm volatile("add $0x8, %esp"); // Get rid of the parameter 'call', leaving us with 'process' which
                                  // is now our return address and so will be popped, leaving us a 
                                  // squeaky clean stack.
  asm volatile("ret"); // Return to user code.
}

static bool processLessThan(Process *p1, Process *p2)
{
  return p1->getLastRun() < p2->getLastRun();
}

ProcessManager::ProcessManager()
{
  currentProcess = NULL;
  nextProcessId = 5;
}


ProcessManager::~ProcessManager()
{
}

//
// init -- Creates one, initial, process. This process has the page directory
//         memoryManager->kernelDirectory.
//
void ProcessManager::init()
{

  readyQueue.setPredicate(processLessThan);

//   Address kstack = (Address) new(true) u8int[KERNEL_STACK_SIZE] + 
//                              KERNEL_STACK_SIZE - 4;
  memoryManager.allocFrame(
        memoryManager.getCurrentDirectory()->getPage(0x500000-0x1000),
                            false);
  memoryManager.allocFrame(
        memoryManager.getCurrentDirectory()->getPage(0x500000-0x2000),
                            false);
  Address kstack = 0x500000-0x4-0x100;

  Process *p = new Process(
                memoryManager.getCurrentDirectory(),  // page dir
                kstack,                               // kernel stack,
                0                                     // Heap breakpoint
               );
  
  processes.add(p->getPid(), p);
  descriptorTables.setKernelStack(kstack);
  currentKernelStack = kstack;
  currentProcess = p;

  // Initialise the resource and functions.
  resource = new Resource((Callable*)this, "ProcessManager");
  resource->add((Resource::Call1)&ProcessManager::getProcessResource, ProcessManager_IF::getProcess);
  
}

//
// switchTo -- Plants asm code to switch memory-space and to change register
//             values.
//
void ProcessManager::switchTo(Process *p)
{
  stopInterrupts();
  currentProcess->setStackPointer(readStackPointer());
  currentProcess->setBasePointer(readBasePointer());
  Address eip = readInstructionPointer();
  
  // If the eip variable seems to be 0x20, that is our magic value
  // that is returned by the pswitch.
  if (eip == 0x20)
  {
    // If we have RPC requests in queue and we are not currently processing
    // RPC requests, plant code to call the RPC executor.
    if (currentProcess->rpcQueue.getLength() &&
        !currentProcess->isProcessingRpcs() )
    {
      plantRpcCaller();
    }
    
    return;
  }

  currentProcess->setInstructionPointer(eip);
  Process* oldProcess = currentProcess;
  currentProcess = p;
  
  currentKernelStack = currentProcess->getKernelStack();
  
  descriptorTables.setKernelStack(currentProcess->getKernelStack());
  memoryManager.setCurrentDirectory(currentProcess->getPageDirectory());
//   kerr << "Switching to " << hex << currentProcess->getPageDirectory()->getPhysical()<<", "<<currentProcess->getInstructionPointer()  << endl;
  switchProcess( currentProcess->getPageDirectory()->getPhysical(),
                 currentProcess->getInstructionPointer(),
                 currentProcess->getStackPointer(),
                 currentProcess->getBasePointer() );
}

//
// shortTermSchedule -- Takes the next process off the ready queue and
//                      switches to it.
//
void ProcessManager::shortTermSchedule()
{
  // If there are any other processes at all
  if (readyQueue.getSize())
  {
    Process *next = readyQueue.getItem(0);
    readyQueue.remove(0);
    // If the current process is not zombie,
    // put it back into the queue.
    if (currentProcess->status != Process::Zombie)
    {
      readyQueue.insert(currentProcess);
    }
    // Update the last run value.
    next->updateLastRun();
    switchTo(next);
  }
}

void ProcessManager::switchToUserMode()
{
  // Just calls asm stublet. Prefix the :: namespace qualifier to force
  // calling the global function, and not ourselves recursively!
  descriptorTables.setKernelStack(currentProcess->getKernelStack());
  ::switchToUserMode();
}

void ProcessManager::plantRpcCaller()
{
  // Uninterruptible
//   stopInterrupts();
  
  // Get the return registers' values
//   if (currentProcess->isInKernel())
//   {
//     // For now we do nothing.
//     Registers *regs = currentProcess->getRegistersPtr();
//     kerr << "regs: " << regs->eip << endl;
//     kerr << "Message to be sent but process in kernel. TODO.\n";
//   }
//   else
//   {
    Registers *regs = currentProcess->getRegistersPtr();
    Address *userStack = (Address *)regs->useresp;
  
    RpcCall *call = currentProcess->rpcQueue.removeFirst();
    
    currentProcess->messageSender = call->sender;

    // Save the register values to the RpcCall object.
    call->eax = regs->eax;
    call->ebx = regs->ebx;
    call->ecx = regs->ecx;
    call->edx = regs->edx;
    call->esi = regs->esi;
    call->edi = regs->edi;
    call->eip = regs->eip;
    call->eflags = regs->eflags;
    
    // Change some of the register values.
    regs->eip = (Address)&executeRpcRequestsWrapper;
    regs->esi = (Address)call;
    regs->edi = (Address)currentProcess;
    
    // This process is about to handle RPC requests, mark it as such.
    currentProcess->processingRpcs.getLock();
//   }

  // Reinterruptible.
//   startInterrupts();

}

void ProcessManager::processDying(u32int pid)
{
  processes.remove(pid);
}

void ProcessManager::yield(s32int pid)
{
  Process *toSchedule = NULL;
  if (pid == -1 && readyQueue.getSize())
  {
    toSchedule = readyQueue.getItem(0);
    readyQueue.remove(0);
  }
  else if (pid > -1)
  {
    toSchedule = processes.lookup(pid);
    if (!toSchedule || toSchedule->status == Process::Zombie)
    {
      return;
    }
    // Find and remove toSchedule from the ready queue.
    for (int iterator = 0; iterator < readyQueue.getSize(); iterator++)
    {
      if (toSchedule == readyQueue.getItem(iterator))
      {
        readyQueue.remove(iterator);
        break;
      }
    }
  }
  else
    return;

  // If the current process is not zombie,
  // put it back into the queue.
  if (currentProcess->status != Process::Zombie)
  {
    readyQueue.insert(currentProcess);
  }
  // Update the last run value.
  currentProcess->setInKernel(false);
  toSchedule->updateLastRun();
  switchTo(toSchedule);
}

u32int ProcessManager::getProcessResource(int pid)
{
  if (pid == -1)
    return currentProcess->resource->getId();
  else
  {
    Process *p = processes.lookup(pid);
    if (p)
      return p->resource->getId();
    else
      return 0;
  }
}
